// websocket 类
import { SystemStore } from "@/store/system"
import { User } from "@/store/user"
import { getFastestUrl } from "@/utils/useStoreMethods";
import { RingBuffer } from "@/net/RingBuffer";
import { MessageMap } from "@/net/MessageMap";
import { getDeviceId } from "@/net/Utils";
import { EncodeUtils } from "@/net/EncodeUtils";
import { NetMsgType } from "@/netBase/NetMsgType";
import { NetPacket } from "@/netBase/NetPacket";
import { NetEnumDef } from "@/netBase/NetEnumDef";
import { IP } from "@/utils/useStoreMethods";
import eventBus from "./eventBus";
import { showNotify } from '@nutui/nutui'
import { Local } from "@/utils/storage";
import { i18n } from "@/languages/index";

const t = i18n.global.t;
const NET_VERSION = 2 // 协议版本，目前写死为2
const systemStore = SystemStore()
const userStore = User()

// 根据协议在什么状态能发送分为3种：1，只能登录前发送；2，只能登录后发送；3，任何状态可以发送
// 这里是不需要判断状态的协议
const freeRequests: any = [
    NetMsgType.msgType['msg_req_check_version'],
    NetMsgType.msgType['msg_req_get_send_sms_check_code'],
    NetMsgType.msgType['msg_req_check_sms_code'],
    NetMsgType.msgType['msg_req_reset_password'],
    NetMsgType.msgType['msg_req_add_guest'],
    NetMsgType.msgType['msg_req_register_captcha'],
    NetMsgType.msgType['msg_req_create_v2'],
    NetMsgType.msgType['msg_req_get_mobile_sms_login_code'],
    NetMsgType.msgType['msg_req_register_account'],
    NetMsgType.msgType['msg_req_login'],
    NetMsgType.msgType['msg_req_role_single_link_login'],
    NetMsgType.msgType['msg_req_login_captcha'],
    NetMsgType.msgType['msg_req_role_login'],
    NetMsgType.msgType['msg_req_role_login_with_ip'],
    NetMsgType.msgType['msg_req_create_role'],
    NetMsgType.msgType['msg_req_init_connect'],
    NetMsgType.msgType['msg_req_login_check'],
]

// 请求池
const QueuePool: any = {
    connectList: [], // 未连接时的请求队列
    loginList: [], // 未登录时的请求队列
    sendingList: [], // 用于记录正在发送的请求
    add2list: (req: any) => { // 添加到队列
        if (req.needLogin) {
            // callbackName 相同即视为同一个请求，不再放入
            const target = QueuePool.loginList.find((item: any) => item.config && req.config && item.config.callbackName && item.config.callbackName == req.config.callbackName)
            if (!target) {
                QueuePool.loginList.push(req)
            }
        } else {
            // callbackName 相同即视为同一个请求，不再放入
            const target = QueuePool.connectList.find((item: any) => item.config && req.config && item.config.callbackName && item.config.callbackName == req.config.callbackName)
            if (!target) {
                QueuePool.connectList.push(req)
            }
        }
    },
    sendAll: (needLogin: Boolean) => { // 发送队列
        const list = (needLogin ? QueuePool.loginList : QueuePool.connectList)
        console.log('%c等待队列' + needLogin, 'color: #f3b40b', list.length)
        list.forEach((req: any) => {
            WsClass.instance.send(req.obj, req.needLogin, req.config)
        })
        if (needLogin) {
            QueuePool.loginList = []
        } else {
            QueuePool.connectList = []
        }
    },
    add2SendingList: (req: any, needLogin: Boolean, config: any) => { // 添加一个请求到发送记录
        let target: any = QueuePool.sendingList.find((item: any) => item.callbackName == config.callbackName)
        if (!target) {
            QueuePool.sendingList.push({
                req,
                needLogin,
                config
            })
            // console.error('---添加请求:' + config.callbackName, QueuePool.sendingList)
        } else {
            target = { // 如果找到同名的，就用新的代替旧的
                req,
                needLogin,
                config
            }
            // console.error('---替换请求:' + config.callbackName, QueuePool.sendingList)
        }
    },
    removeFromSendingList: (msgID: any, message?: any) => { // 从发送记录里移除这个请求
        const target = QueuePool.sendingList.find((item: any) => item.config.callbackName == getMsgType(msgID))
        if (target && target.config && target.config.callback) target.config.callback(message)
        if (target) {
            QueuePool.sendingList = QueuePool.sendingList.filter((item: any) => item.config.callbackName != getMsgType(msgID))
            // console.error('---移除请求:' + getMsgType(msgID), QueuePool.sendingList)
        }
    },
    clearAll: () => { // 重连后要清空全部
        QueuePool.connectList = []
        QueuePool.loginList = []
        QueuePool.sendingList = []
    }
}

// ws类
export class WsClass {
    // 单例
    private static _instance: WsClass
    public static get instance(): WsClass {
        if (!WsClass._instance) {
            WsClass._instance = new WsClass()
        }
        return WsClass._instance
    }
    private ip: string = ''; // ws地址
    private ws?: WebSocket; // ws实例
    private ringBuffer = new RingBuffer();
    private packageCount = 1; // 消息包编号
    private loading: boolean = false // ws连接中
    private fasterLoading: boolean = false // 获取最快地址的loading
    private pause: boolean = false // 主动断开时使用，再次发送请求会重新激活连接

    // 构造
    private constructor() {
        this.reset()
    }
    // 重置状态
    private reset() {
        this.packageCount = 1;
        systemStore.setWsConnected(false)
        systemStore.setLoggedIn(false)
        QueuePool.clearAll()
    }
    // 初始化
    init() {
        if (this.pause) return
        if (this.loading || this.fasterLoading) return
        this.loading = true
        this.fasterLoading = true
        return new Promise(resolve => {
            // 重置连接和登录状态
            this.reset()
            const getUrl = () => {
                console.log('%c-- 检测最快地址中 --', 'color: #f3b40b')
                getFastestUrl().then(res => {
                    this.fasterLoading = false
                    this.ip = res
                    this.connect()
                    resolve(res)
                }).catch(() => {
                    setTimeout(() => {
                        this.fasterLoading = false
                        // 如果获取最速ip失败，就等一会重新再获取
                        getUrl()
                    }, 3000)
                })
            }
            getUrl()
        })
    }
    // 连接ws
    connect() {
        console.log('%c--- 开始连接 ---', 'color: #f3b40b', this.ip)
        this.ws = new WebSocket(this.ip, []);
        this.ws.onopen = () => {
            this.loading = false
            console.log('%c--- onopen ---', 'color: #f3b40b')
            systemStore.setWsConnected(true)
            eventBus.emit('wsopen')
            // 发送第一个请求-版本检测
            setTimeout(() => {
                const req = NetPacket.req_check_version()
                req.version = NET_VERSION
                this.send(req, false)
            }, 0)
            // 清空回调队列
            setTimeout(() => {
                if (QueuePool.sendingList.length) {
                    QueuePool.sendingList.forEach((item: any) => {
                        this.send(item.req, item.needLogin, item.config)
                    })
                }
            }, 100)
            // 清空等待队列
            setTimeout(() => {
                QueuePool.sendAll(false)
            }, 300)

        };
        this.ws.onclose = res => {
            console.error('--- onclose ---', res)
            this.reset()
            // 主动断开不需要重连
            if (this.pause) return
            setTimeout(() => {
                this.loading = false
                this.init()
            }, 2000)
        };
        this.ws.onmessage = event => {
            if (typeof event.data == "string") {
                let array = [];
                for (let i = 0; i < event.data.length; i++) {
                    array.push(event.data.charCodeAt(i));
                }
                let buf = new Uint8Array(array);
                this.ReceiveMessage(buf);
                return;
            }
            let reader = new FileReader();
            reader.readAsArrayBuffer(event.data);
            reader.onload = () => {
                let buf = new Uint8Array(reader.result as any);
                //先存入队列中
                this.ReceiveMessage(buf);
            };
        };
        this.ws.onerror = res => {
            console.error('--- onerror ---', res)
            this.reset()
            setTimeout(() => {
                this.loading = false
                this.init()
            }, 2000)
        };
    }
    // 主动断开的重连
    reConnectWs() {
        if (!this.pause) return
        this.pause = false
        this.connect()
    }
    // 暂时断开，用于进入游戏
    pauseWs() {
        this.pause = true
        try {
            this.ws?.close()
            console.error('断开成功')
        } catch {
            console.error('断开失败')
        }
    }
    // 接收到的二进制消息
    ReceiveMessage(buf: any) {
        var len = buf.byteLength;
        if (buf == undefined || buf == null || len == 0) {
            return;
        }
        for (var i = 0; i < len; ++i) {
            this.ringBuffer.Push(buf[i]);
        }
        while (true) {
            var receivedlength = this.ringBuffer.DataLength();
            if (receivedlength < 4) {
                return;
            }

            let bodyLen = this.ringBuffer.decode_msg_total_length();

            if (bodyLen < 1) {
                return;
            }

            if (receivedlength < bodyLen + 4) {
                return;
            }
            this.DecodeMessage(bodyLen);
        }
    }
    // 解码消息体
    DecodeMessage(bodyLen: number) {
        this.ringBuffer.addHead(4); //先去掉4个字节的包长度，再开始解析msg_id
        let msgID = this.ringBuffer.decode_msg_id();

        let messageStruct = MessageMap.getMessage(msgID);
        if (messageStruct == null) {
            //找不到对应的解码结构体时，循环消息ringbuffer的标识也要往后移，跳过这条消息的数据
            this.ringBuffer.addHead(bodyLen - 4);
            console.log("msgID not register", msgID);
            return;
        }
        let message = this.ringBuffer.decode_msg_body(messageStruct, bodyLen - 4);
        if (message.code != 1) {
            if (message.message) {
                const notShowMsg = ['account_type_error', 'withdraw_password_can_be_bound', 'get_withdraw_password_status_success', 'withdraw_password_open_success', 'withdraw_password_operate_failed']
                if (!notShowMsg.includes(message.message)) {
                    const errorContent = message.message || t('home_all_internet_error') + `: ${msgID}`
                    showNotify.danger(t(errorContent))
                }
            }
        }
        if (![1004].includes(msgID)) { // 心跳消息不打印
            // console.log(`返回 %c${msgID} `, 'color:#1dc51d')
            console.log(`返回 %c${getMsgType(msgID)}`, 'color:#1dc51d')
            console.log(JSON.parse(JSON.stringify(message)))
        }
        // if (msgID == 5) { // 这个事件id监听不到  登录完成事件
        //     eventBus.emit('10086', message)
        // }
        // 从历史请求中移除
        QueuePool.removeFromSendingList(msgID, message)
        // 抛出事件
        eventBus.emit(getMsgType(msgID), message)


    }



    // 发送消息
    // obj-NetPacket获取的请求体   needLogin-是否需要登录  config-其他请求选项
    /*
    * config 的字段如下：
    * 
    *   callbackName: string, // 是否开启请求的回调协议监听，开启后如果发送请求未成功返回，会放入历史列表，下次ws连接时会自动重新请求，用于一些必须要自动获取到的数据。传入该请求返回时监听的协议名字。比如发起协议 msg_req_check_version 这里 callbackName的值就是 msg_notify_check_version
    *   callback: func, // 回调函数，单独传无效，必须与 callbackName 同时传，返回的协议匹配到 callbackName 时执行
    *   
    * */
    send(obj: any, needLogin: Boolean, config?: any) {
        const reqId = obj.getMsgID() // 请求id
        if (needLogin && !freeRequests.includes(reqId)) { // 检测登录状态
            if (!systemStore.isServerLoggedIn) { // 未登录，放到 未登录请求池
                QueuePool.add2list({ obj, needLogin, config })
                return
            }
        } else { // 检测连接状态
            if (!systemStore.isWsConnected) { // 未连接，放到 未连接请求池
                QueuePool.add2list({ obj, needLogin, config })
                return
            }
            if (this.pause) { // 暂停状态，主动去连接
                this.pause = false
                setTimeout(() => {
                    console.error('重新激活WS')
                    this.init()
                }, 0)
            }
        }

        // 发送请求
        if (config && config.callbackName) {// 存入请求历史
            QueuePool.add2SendingList(obj, needLogin, config)
        }
        this.sendRequest(obj)
    }
    sendRequest(data: any) {
        const msg = JSON.parse(JSON.stringify(data))
        delete msg.build
        delete msg.decode
        delete msg.encode
        delete msg.getMsgID
        console.log(`发送 %c${getMsgType(data.getMsgID())}`, 'color:red')
        console.log(msg)

        // 消息包编号，服务器需要用到
        let buf: any = [];
        let count = this.calcPacketCount();
        EncodeUtils.uInt8ToByte(count, buf);

        // 编码消息体
        data.build(buf);

        //编码整个包头，不包括自身的4个字节
        let bodylen = EncodeUtils.swab32_msg_total_length(buf.length);
        let bodyLenBuf: any[] = [];
        EncodeUtils.encode_msg_total_length(bodylen, bodyLenBuf);

        //把bodylen放到buf最前面
        for (let i = 3; i >= 0; --i) {
            buf.unshift(bodyLenBuf[i]);
        }

        let sendbuffer = new Uint8Array(buf).buffer;
        this.ws?.send(sendbuffer);
    }


    calcPacketCount() {
        let count = this.packageCount++;
        if (this.packageCount > 255) {
            this.packageCount = 1;
        }

        return count;
    }
}

export default WsClass;

// 通过id获取消息的key
function getMsgType(msgID: number) {
    let msgType: any = NetMsgType.msgType
    for (const key in msgType) {
        if (msgType[key] == msgID) {
            return key;
        }
    }
    return 'unknownType'
}


// 监听未知的事件
eventBus.on('unknownType', res => {
    console.error('-- 接收到未知消息 --', res)
})

/* 监听一些全局事件 */


eventBus.on('msg_notify_check_version', (res: any) => { // 版本检测
    if (res.result == 2) { // 版本信息异常
        showNotify.danger(t('home_all_version_error'))
        setInterval(() => {
            showNotify.danger(t('home_all_version_error'))
        }, 6000)
        return
    }
    // 如果本地已经登录了，那么同步服务端状态
    if (userStore.token) {
        // 登录服务器
        syncLoginStatusFromServe()
    }
})
eventBus.on('msg_nodify_login', (res: any) => { // 登录成功，需要同步服务器登录状态
    if (res.code == 1) {
        // 登录服务器
        syncLoginStatusFromServe()
        // 发送邀请码
        const agent_id = localStorage.getItem("agent_id");
        if (Number(agent_id)) {
            const rq = NetPacket.req_set_invitecode();
            rq.superior_id = agent_id as string;
            WsClass.instance.send(rq, true);
        }
    }
})

eventBus.on('msg_notify_login_result', (res: any) => { // 监听登录成功，然后释放登录等待池
    if (res && res.result == 1) { // 登录成功
        systemStore.setLoggedIn(true)
        const req_user_info = NetPacket.req_user_info();
        setTimeout(() => {
            // 同步用户数据
            WsClass.instance.send(req_user_info, true, {
                callbackName: 'msg_notify_user_info',
                callback: (data: any) => {
                    userStore.getInfo(data);
                }
            })
            // 同步会员信息
            const vipInfoReq = NetPacket.req_vip_info();
            WsClass.instance.send(vipInfoReq, true, {
                callbackName: 'msg_notify_vip_info',
                callback: (data: any) => {
                    userStore.getVIPInfo(data)
                }
            })
        }, 1000)
        setTimeout(() => {
            QueuePool.sendAll(true)
        }, 2000)
    } else { // 登录失败，清空缓存，退出登录状态
        if (!res.hideTip) { // 主动触发时不用提示信息
            showNotify.danger(t('home_all_login_error'))
        }
        setTimeout(() => {
            Local.remove("user");
            systemStore.setWsConnected(false)
            systemStore.setLoggedIn(false)
            userStore.getUserLoginInfo(null);
            location.href = "/";
        }, res.hideTip?400:1000)
    }
})

async function syncLoginStatusFromServe() {
    // 同步登录状态
    let tb_req = NetPacket.req_role_login_with_ip();
    tb_req.uid = String(userStore.userInfo.user_id);
    tb_req.server_id = 2;
    tb_req.token = userStore.userInfo.token;
    tb_req.type = NetEnumDef.connect_type.re_connect;
    tb_req.version = NET_VERSION;
    tb_req.device_id = await getDeviceId();
    tb_req.ip = await IP();
    WsClass.instance.send(tb_req, false)
    // 记录下登录时间，如果2分钟内收到重复登录则不弹出
    sessionStorage.setItem('login_time', new Date().getTime().toString())
}